module dice;

import std.random;
import std.string;
import std.regexp;
import std.conv;

/**
 * Rolls dice with the given amount of sides the number of num times. Adds the bonus after rolling.
 * Examples: 
 *		rollDice(1, 4) results in a value between 1 and 4.<br>
 *		rollDice(2, 6, 5) results in a value between 7 and 17.<br>
 *		rollDice(1, 8, -2) results in a value between -1 and 6.
 *
 * Params:
 *	num = the number of dice rolls
 *  sides = how many sides the die has
 *  bonus = a possible bonus/penalty which is added to the result
 *
 * Returns: The value of the dice roll.
 */
public int rollDice(uint num, uint sides, int bonus = 0)
in
{
	assert (sides > 0);
}
body
{
	int res;
	for (int i = 0; i < num; i++)
	{
		res += uniform(1, sides + 1);
	}
	return res + bonus;
}

/**
 * Rolls dice as defined by the format parameter. Adds the bonus after rolling.
 * Examples:
 *		rollDice("1d4") results in a value between 1 and 4.<br>
 *		rollDice("1d4+1") results in a value between 2 and 5.<br>
 *		rollDice("1d4-1") results in a value between 0 and 3.<br>
 *		rollDice("1d4", 2) results in a value between 3 and 6.<br>
 *		rollDice("1d4+1", 2) results in a value between 4 and 7.
 *
 * Params:
 *	format = the format of the die roll(s)
 *	bonus = a possible bonus/penalty which is added to the result
 *
 * Returns: The value of the dice roll.
 */
public int rollDice(string format, int bonus = 0)	
in
{
	assert (format !is null);
	assert (format != "");
}
body		
{
	uint num, sides;
	int parsedBonus;
	// parse the dice string
	parseDice(format, num, sides, parsedBonus);
	// Roll the dice
	return rollDice(num, sides, bonus + parsedBonus);
}

/**
 * Parses strings like "2d6", "1d4+2" and "3d8 - 5".
 */
private void parseDice(string format, out uint num, out uint sides, out int parsedBonus)
{
	format = normalize(format);
	
	// Remove any whitespace
	format = replace(format, " ", "");
	
	int bonusType = format.indexOf("-") == -1 ? 1 : -1;
	
	// Split at the 'd'
	auto args = split(format, RegExp("d"));
	
	// Get the number of throws
	num = to!(uint)(args[0]);
	
	// Split at '+' or '-'
	args = split(args[1], RegExp("\\+|-"));
	
	sides = to!(uint)(args[0]);
	parsedBonus = to!(uint)(args[1]) * bonusType;
}

/**
 * Normalizes the dice str to the format "x1dx2+x3".
 */
private string normalize(string diceStr)
{
	if (diceStr.indexOf("+") == -1 && diceStr.indexOf("-") == -1)
	{
		// Add "+0" to the end to make it complete
		return diceStr ~ "+0";
	}
	return diceStr;
}

unittest
{
	auto roll = rollDice(1, 4);
	assert (roll >= 1 && roll <= 4);
	
	roll = rollDice(2, 6, 3);
	assert (roll >= 5 && roll <= 15);
	
	roll = rollDice(1, 8, -2);
	assert (roll >= -1 && roll <= 6);
	
	roll = rollDice("2d4");
	assert (roll >= 2 && roll <= 8);
	
	roll = rollDice("2d4+2");
	assert (roll >= 4 && roll <= 10);
	
	roll = rollDice("2d4-2");
	assert (roll >= 0 && roll <= 6);	
	
	roll = rollDice("2d4", 2);
	assert (roll >= 4 && roll <= 10);

	roll = rollDice("2d4-1", 2);
	assert (roll >= 3 && roll <= 9);

	roll = rollDice("1d6+2");
	assert (roll >= 3 && roll <= 8);
	
	roll = rollDice("1d4 - 1");
	assert (roll >= 0 && roll <= 3);
}
